// Define packages:
var sigma = {};
sigma.tools = {};
sigma.classes = {};
sigma.instances = {};

// Adding Array helpers, if not present yet:
(function() {
	if(!Array.prototype.some) {
		Array.prototype.some = function(fun /*, thisp*/) {
			var len = this.length;
			if( typeof fun != 'function') {
				throw new TypeError();
			}

			var thisp = arguments[1];
			for(var i = 0; i < len; i++) {
				if( i in this && fun.call(thisp, this[i], i, this)) {
					return true;
				}
			}

			return false;
		};
	}

	if(!Array.prototype.forEach) {
		Array.prototype.forEach = function(fun /*, thisp*/) {
			var len = this.length;
			if( typeof fun != 'function') {
				throw new TypeError();
			}

			var thisp = arguments[1];
			for(var i = 0; i < len; i++) {
				if( i in this) {
					fun.call(thisp, this[i], i, this);
				}
			}
		};
	}

	if(!Array.prototype.map) {
		Array.prototype.map = function(fun /*, thisp*/) {
			var len = this.length;
			if( typeof fun != 'function') {
				throw new TypeError();
			}

			var res = new Array(len);
			var thisp = arguments[1];
			for(var i = 0; i < len; i++) {
				if( i in this) {
					res[i] = fun.call(thisp, this[i], i, this);
				}
			}

			return res;
		};
	}

	if(!Array.prototype.filter) {
		Array.prototype.filter = function(fun /*, thisp*/) {
			var len = this.length;
			if( typeof fun != 'function')
				throw new TypeError();

			var res = new Array();
			var thisp = arguments[1];
			for(var i = 0; i < len; i++) {
				if( i in this) {
					var val = this[i];
					// in case fun mutates this
					if(fun.call(thisp, val, i, this)) {
						res.push(val);
					}
				}
			}

			return res;
		};
	}

	if(!Object.keys) {
		Object.keys = (function() {
			var hasOwnProperty = Object.prototype.hasOwnProperty, hasDontEnumBug = !( {
				toString : null
			}).propertyIsEnumerable('toString'), dontEnums = ['toString', 'toLocaleString', 'valueOf', 'hasOwnProperty', 'isPrototypeOf', 'propertyIsEnumerable', 'constructor'], dontEnumsLength = dontEnums.length;

			return function(obj) {
				if( typeof obj !== 'object' && typeof obj !== 'function' || obj === null) {
					throw new TypeError('Object.keys called on non-object');
				}

				var result = [];

				for(var prop in obj) {
					if(hasOwnProperty.call(obj, prop))
						result.push(prop);
				}

				if(hasDontEnumBug) {
					for(var i = 0; i < dontEnumsLength; i++) {
						if(hasOwnProperty.call(obj, dontEnums[i])) {
							result.push(dontEnums[i]);
						}
					}
				}
				return result;
			}
		})();
	}
})();

//////////////////////////////////////////
/**
 * A jQuery like properties management class. It works like jQuery .css()
 * method: You can call it with juste one string to get the corresponding
 * property, with a string and anything else to set the corresponding property,
 * or directly with an object, and then each pair string / object (or any type)
 * will be set in the properties.
 * @constructor
 * @this {sigma.classes.Cascade}
 */
sigma.classes.Cascade = function() {
	/**
	 * This instance properties.
	 * @protected
	 * @type {Object}
	 */
	this.p = {};

	/**
	 * The method to use to set/get any property of this instance.
	 * @param  {(string|Object)} a1 If it is a string and if a2 is undefined,
	 *                              then it will return the corresponding
	 *                              property.
	 *                              If it is a string and if a2 is set, then it
	 *                              will set a2 as the property corresponding to
	 *                              a1, and return this.
	 *                              If it is an object, then each pair string /
	 *                              object (or any other type) will be set as a
	 *                              property.
	 * @param  {*?} a2              The new property corresponding to a1 if a1 is
	 *                              a string.
	 * @return {(*|sigma.classes.Cascade)} Returns itself or the corresponding
	 *                                     property.
	 */
	this.config = function(a1, a2) {
		if( typeof a1 == 'string' && a2 == undefined) {
			return this.p[a1];
		} else {
			var o = ( typeof a1 == 'object' && a2 == undefined) ? a1 : {};
			if( typeof a1 == 'string') {
				o[a1] = a2;
			}

			for(var k in o) {
				if(this.p[k] != undefined) {
					this.p[k] = o[k];
				}
			}
			return this;
		}
	};
};

///////////////////////////////////////////////////////
/**
 * sigma.js custom event dispatcher class.
 * @constructor
 * @this {sigma.classes.EventDispatcher}
 */
sigma.classes.EventDispatcher = function() {
	/**
	 * An object containing all the different handlers bound to one or many
	 * events, indexed by these events.
	 * @private
	 * @type {Object.<string,Object>}
	 */
	var _h = {};

	/**
	 * Represents "this", without the well-known scope issue.
	 * @private
	 * @type {sigma.classes.EventDispatcher}
	 */
	var _self = this;

	/**
	 * Will execute the handler the next (and only the next) time that the
	 * indicated event (or the indicated events) will be triggered.
	 * @param  {string} events            The name of the event (or the events
	 *                                    separated by spaces).
	 * @param  {function(Object)} handler The handler to bind.
	 * @return {sigma.classes.EventDispatcher} Returns itself.
	 */
	function one(events, handler) {
		if(!handler || !events) {
			return _self;
		}

		var eArray = (( typeof events) == 'string') ? events.split(' ') : events;

		eArray.forEach(function(event) {
			if(!_h[event]) {
				_h[event] = [];
			}

			_h[event].push({
				'h' : handler,
				'one' : true
			});
		});

		return _self;
	}

	/**
	 * Will execute the handler everytime that the indicated event (or the
	 * indicated events) will be triggered.
	 * @param  {string} events            The name of the event (or the events
	 *                                    separated by spaces).
	 * @param  {function(Object)} handler The handler to bind.
	 * @return {sigma.classes.EventDispatcher} Returns itself.
	 */
	function bind(events, handler) {
		if(!handler || !events) {
			return _self;
		}

		var eArray = (( typeof events) == 'string') ? events.split(' ') : events;

		eArray.forEach(function(event) {
			if(!_h[event]) {
				_h[event] = [];
			}

			_h[event].push({
				'h' : handler,
				'one' : false
			});
		});

		return _self;
	}

	/**
	 * Unbinds the handler from a specified event (or specified events).
	 * @param  {?string} events            The name of the event (or the events
	 *                                     separated by spaces). If undefined,
	 *                                     then all handlers are unbound.
	 * @param  {?function(Object)} handler The handler to unbind. If undefined,
	 *                                     each handler bound to the event or the
	 *                                     events will be unbound.
	 * @return {sigma.classes.EventDispatcher} Returns itself.
	 */
	function unbind(events, handler) {
		if(!events) {
			_h = {};
		}

		var eArray = typeof events == 'string' ? events.split(' ') : events;

		if(handler) {
			eArray.forEach(function(event) {
				if(_h[event]) {
					_h[event] = _h[event].filter(function(e) {
						return e['h'] != handler;
					});
				}

				if(_h[event] && _h[event].length == 0) {
					delete _h[event];
				}
			});
		} else {
			eArray.forEach(function(event) {
				delete _h[event];
			});
		}

		return _self;
	}

	/**
	 * Executes each handler bound to the event
	 * @param  {string} type     The type of the event.
	 * @param  {?Object} content The content of the event (optional).
	 * @return {sigma.classes.EventDispatcher} Returns itself.
	 */
	function dispatch(type, content) {
		if(_h[type]) {
			_h[type].forEach(function(e) {
				e['h']({
					'type' : type,
					'content' : content,
					'target' : _self
				});
			});

			_h[type] = _h[type].filter(function(e) {
				return !e['one'];
			});
		}

		return _self;
	}

	/* PUBLIC INTERFACE: */
	this.one = one;
	this.bind = bind;
	this.unbind = unbind;
	this.dispatch = dispatch;
};

///////////////////////////////////////////
(
	function() {
		// Define local shortcut:
		var id = 0;

		// Define local package:
		var local = {};
		local.plugins = [];

		sigma.init = function(dom) {
			var inst = new Sigma(dom, (++id).toString());
			sigma.instances[id] = new SigmaPublic(inst);
			return sigma.instances[id];
		};

		///////////////////////////////
		/**
		 * The graph data model used in sigma.js.
		 * @constructor
		 * @extends sigma.classes.Cascade
		 * @extends sigma.classes.EventDispatcher
		 * @this {Graph}
		 */
		function Graph() {
			sigma.classes.Cascade.call(this);
			sigma.classes.EventDispatcher.call(this);

			/**
			 * Represents "this", without the well-known scope issue.
			 * @private
			 * @type {Graph}
			 */
			var self = this;

			/**
			 * The different parameters that determine how the nodes and edges should be
			 * translated and rescaled.
			 * @type {Object}
			 */
			this.p = {
				minNodeSize : 0,
				maxNodeSize : 0,
				minEdgeSize : 0,
				maxEdgeSize : 0,
				//   Scaling mode:
				//   - 'inside' (default)
				//   - 'outside'
				scalingMode : 'inside',
				nodesPowRatio : 0.5,
				edgesPowRatio : 0
			};

			/**
			 * Contains the borders of the graph. These are useful to avoid the user to
			 * drag the graph out of the canvas.
			 * @type {Object}
			 */
			this.borders = {};

			/**
			 * Inserts a node in the graph.
			 * @param {string} id     The node's ID.
			 * @param {object} params An object containing the different parameters
			 *                        of the node.
			 * @return {Graph} Returns itself.
			 */
			function addNode(id, params) {
				if(self.nodesIndex[id]) {
					throw new Error('Node "' + id + '" already exists.');
				}

				params = params || {};
				var n = {
					// Numbers :
					'x' : 0,
					'y' : 0,
					'size' : 1,
					'degree' : 0,
					'inDegree' : 0,
					'outDegree' : 0,
					// Flags :
					'fixed' : false,
					'active' : false,
					'hidden' : false,
					'forceLabel' : false,
					// Strings :
					'label' : id.toString(),
					'id' : id.toString(),
					// Custom attributes :
					'attr' : {}
				};

				for(var k in params) {
					switch (k) {
						case 'id':
							break;
						case 'x':
						case 'y':
						case 'size':
							n[k] = +params[k];
							break;
						case 'fixed':
						case 'active':
						case 'hidden':
						case 'forceLabel':
							n[k] = !!params[k];
							break;
						case 'color':
						case 'label':
							n[k] = params[k];
							break;
						default:
							n['attr'][k] = params[k];
					}
				}

				self.nodes.push(n);
				self.nodesIndex[id.toString()] = n;

				return self;
			};

			/**
			 * Generates the clone of a node, to make it easier to be exported.
			 * @private
			 * @param  {Object} node The node to clone.
			 * @return {Object} The clone of the node.
			 */
			function cloneNode(node) {
				return {
					'x' : node['x'],
					'y' : node['y'],
					'size' : node['size'],
					'degree' : node['degree'],
					'inDegree' : node['inDegree'],
					'outDegree' : node['outDegree'],
					'displayX' : node['displayX'],
					'displayY' : node['displayY'],
					'displaySize' : node['displaySize'],
					'label' : node['label'],
					'id' : node['id'],
					'color' : node['color'],
					'fixed' : node['fixed'],
					'active' : node['active'],
					'hidden' : node['hidden'],
					'forceLabel' : node['forceLabel'],
					'attr' : node['attr']
				};
			};

			/**
			 * Checks the clone of a node, and inserts its values when possible. For
			 * example, it is possible to modify the size or the color of a node, but it
			 * is not possible to modify its display values or its id.
			 * @private
			 * @param  {Object} node The original node.
			 * @param  {Object} copy The clone.
			 * @return {Graph} Returns itself.
			 */
			function checkNode(node, copy) {
				for(var k in copy) {
					switch (k) {
						case 'id':
						case 'attr':
						case 'degree':
						case 'inDegree':
						case 'outDegree':
						case 'displayX':
						case 'displayY':
						case 'displaySize':
							break;
						case 'x':
						case 'y':
						case 'size':
							node[k] = +copy[k];
							break;
						case 'fixed':
						case 'active':
						case 'hidden':
						case 'forceLabel':
							node[k] = !!copy[k];
							break;
						case 'color':
						case 'label':
							node[k] = (copy[k] || '').toString();
							break;
						default:
							node['attr'][k] = copy[k];
					}
				}

				return self;
			};

			/**
			 * Deletes one or several nodes from the graph, and the related edges.
			 * @param  {(string|Array.<string>)} v A string ID, or an Array of several
			 *                                     IDs.
			 * @return {Graph} Returns itself.
			 */
			function dropNode(v) {
				var a = ( v instanceof Array ? v : [v]) || [];

				a.forEach(function(id) {
					if(self.nodesIndex[id]) {
						var index = null;
						self.nodes.some(function(n, i) {
							if(n['id'] == id) {
								index = i;
								return true;
							}
							return false;
						});

						index != null && self.nodes.splice(index, 1);
						delete self.nodesIndex[id];

						var edgesToRemove = [];
						self.edges = self.edges.filter(function(e) {
							if(e['source']['id'] == id) {
								delete self.edgesIndex[e['id']];
								e['target']['degree']--;
								e['target']['inDegree']--;
								return false;
							} else if(e['target']['id'] == id) {
								delete self.edgesIndex[e['id']];
								e['source']['degree']--;
								e['source']['outDegree']--;
								return false;
							}
							return true;
						});
					} else {
						sigma.log('Node "' + id + '" does not exist.');
					}
				});

				return self;
			};

			/**
			 * Inserts an edge in the graph.
			 * @param {string} id     The edge ID.
			 * @param {string} source The ID of the edge source.
			 * @param {string} target The ID of the edge target.
			 * @param {object} params An object containing the different parameters
			 *                        of the edge.
			 * @return {Graph} Returns itself.
			 */
			function addEdge(id, source, target, params) {
				if(self.edgesIndex[id]) {
					throw new Error('Edge "' + id + '" already exists.');
				}

				if(!self.nodesIndex[source]) {
					var s = 'Edge\'s source "' + source + '" does not exist yet.';
					throw new Error(s);
				}

				if(!self.nodesIndex[target]) {
					var s = 'Edge\'s target "' + target + '" does not exist yet.';
					throw new Error(s);
				}

				params = params || {};
				var e = {
					'source' : self.nodesIndex[source],
					'target' : self.nodesIndex[target],
					'size' : 1,
					'weight' : 1,
					'displaySize' : 0.5,
					'label' : id.toString(),
					'id' : id.toString(),
					'hidden' : false,
					'attr' : {}
				};

				e['source']['degree']++;
				e['source']['outDegree']++;
				e['target']['degree']++;
				e['target']['inDegree']++;

				for(var k in params) {
					switch (k) {
						case 'id':
						case 'source':
						case 'target':
							break;
						case 'hidden':
							e[k] = !!params[k];
							break;
						case 'size':
						case 'weight':
							e[k] = +params[k];
							break;
						case 'color':
							e[k] = params[k].toString();
							break;
						case 'type':
							e[k] = params[k].toString();
							break;
						case 'label':
							e[k] = params[k];
							break;
						default:
							e['attr'][k] = params[k];
					}
				}

				self.edges.push(e);
				self.edgesIndex[id.toString()] = e;

				return self;
			};

			/**
			 * Generates the clone of a edge, to make it easier to be exported.
			 * @private
			 * @param  {Object} edge The edge to clone.
			 * @return {Object} The clone of the edge.
			 */
			function cloneEdge(edge) {
				return {
					'source' : edge['source']['id'],
					'target' : edge['target']['id'],
					'size' : edge['size'],
					'type' : edge['type'],
					'weight' : edge['weight'],
					'displaySize' : edge['displaySize'],
					'label' : edge['label'],
					'hidden' : edge['hidden'],
					'id' : edge['id'],
					'attr' : edge['attr'],
					'color' : edge['color']
				};
			};

			/**
			 * Checks the clone of an edge, and inserts its values when possible. For
			 * example, it is possible to modify the label or the type of an edge, but it
			 * is not possible to modify its display values or its id.
			 * @private
			 * @param  {Object} edge The original edge.
			 * @param  {Object} copy The clone.
			 * @return {Graph} Returns itself.
			 */
			function checkEdge(edge, copy) {
				for(var k in copy) {
					switch (k) {
						case 'id':
						case 'displaySize':
							break;
						case 'weight':
						case 'size':
							edge[k] = +copy[k];
							break;
						case 'source':
						case 'target':
							edge[k] = self.nodesIndex[k] || edge[k];
							break;
						case 'hidden':
							edge[k] = !!copy[k];
							break;
						case 'color':
						case 'label':
						case 'type':
							edge[k] = (copy[k] || '').toString();
							break;
						default:
							edge['attr'][k] = copy[k];
					}
				}

				return self;
			};

			/**
			 * Deletes one or several edges from the graph.
			 * @param  {(string|Array.<string>)} v A string ID, or an Array of several
			 *                                     IDs.
			 * @return {Graph} Returns itself.
			 */
			function dropEdge(v) {
				var a = ( v instanceof Array ? v : [v]) || [];

				a.forEach(function(id) {
					if(self.edgesIndex[id]) {
						self.edgesIndex[id]['source']['degree']--;
						self.edgesIndex[id]['source']['outDegree']--;
						self.edgesIndex[id]['target']['degree']--;
						self.edgesIndex[id]['target']['inDegree']--;

						var index = null;
						self.edges.some(function(n, i) {
							if(n['id'] == id) {
								index = i;
								return true;
							}
							return false;
						});

						index != null && self.edges.splice(index, 1);
						delete self.edgesIndex[id];
					} else {
						sigma.log('Edge "' + id + '" does not exist.');
					}
				});

				return self;
			};

			/**
			 * Deletes every nodes and edges from the graph.
			 * @return {Graph} Returns itself.
			 */
			function empty() {
				self.nodes = [];
				self.nodesIndex = {};
				self.edges = [];
				self.edgesIndex = {};

				return self;
			};

			/**
			 * Computes the display x, y and size of each node, relatively to the
			 * original values and the borders determined in the parameters, such as
			 * each node is in the described area.
			 * @param  {number} w           The area width (actually the width of the DOM
			 *                              root).
			 * @param  {number} h           The area height (actually the height of the
			 *                              DOM root).
			 * @param  {boolean} parseNodes Indicates if the nodes have to be parsed.
			 * @param  {boolean} parseEdges Indicates if the edges have to be parsed.
			 * @return {Graph} Returns itself.
			 */
			function rescale(w, h, parseNodes, parseEdges) {
				var weightMax = 0, sizeMax = 0;

				parseNodes && self.nodes.forEach(function(node) {
					sizeMax = Math.max(node['size'], sizeMax);
				});

				parseEdges && self.edges.forEach(function(edge) {
					weightMax = Math.max(edge['size'], weightMax);
				});

				sizeMax = sizeMax || 1;
				weightMax = weightMax || 1;

				// Recenter the nodes:
				var xMin, xMax, yMin, yMax;
				parseNodes && self.nodes.forEach(function(node) {
					xMax = Math.max(node['x'], xMax || node['x']);
					xMin = Math.min(node['x'], xMin || node['x']);
					yMax = Math.max(node['y'], yMax || node['y']);
					yMin = Math.min(node['y'], yMin || node['y']);
				});

				// First, we compute the scaling ratio, without considering the sizes
				// of the nodes : Each node will have its center in the canvas, but might
				// be partially out of it.
				var scale = self.p.scalingMode == 'outside' ? Math.max(w / Math.max(xMax - xMin, 1), h / Math.max(yMax - yMin, 1)) : Math.min(w / Math.max(xMax - xMin, 1), h / Math.max(yMax - yMin, 1));

				// Then, we correct that scaling ratio considering a margin, which is
				// basically the size of the biggest node.
				// This has to be done as a correction since to compare the size of the
				// biggest node to the X and Y values, we have to first get an
				// approximation of the scaling ratio.
				var margin = (self.p.maxNodeSize || sizeMax) / scale;
				xMax += margin;
				xMin -= margin;
				yMax += margin;
				yMin -= margin;

				scale = self.p.scalingMode == 'outside' ? Math.max(w / Math.max(xMax - xMin, 1), h / Math.max(yMax - yMin, 1)) : Math.min(w / Math.max(xMax - xMin, 1), h / Math.max(yMax - yMin, 1));

				// Size homothetic parameters:
				var a, b;
				if(!self.p.maxNodeSize && !self.p.minNodeSize) {
					a = 1;
					b = 0;
				} else if(self.p.maxNodeSize == self.p.minNodeSize) {
					a = 0;
					b = self.p.maxNodeSize;
				} else {
					a = (self.p.maxNodeSize - self.p.minNodeSize) / sizeMax;
					b = self.p.minNodeSize;
				}

				var c, d;
				if(!self.p.maxEdgeSize && !self.p.minEdgeSize) {
					c = 1;
					d = 0;
				} else if(self.p.maxEdgeSize == self.p.minEdgeSize) {
					c = 0;
					d = self.p.minEdgeSize;
				} else {
					c = (self.p.maxEdgeSize - self.p.minEdgeSize) / weightMax;
					d = self.p.minEdgeSize;
				}

				// Rescale the nodes:
				parseNodes && self.nodes.forEach(function(node) {
					node['displaySize'] = node['size'] * a + b;

					if(!node['fixed']) {
						node['displayX'] = (node['x'] - (xMax + xMin) / 2) * scale + w / 2;
						node['displayY'] = (node['y'] - (yMax + yMin) / 2) * scale + h / 2;
					}
				});

				parseEdges && self.edges.forEach(function(edge) {
					edge['displaySize'] = edge['size'] * c + d;
				});

				return self;
			};

			/**
			 * Translates the display values of the nodes and edges relatively to the
			 * scene position and zoom ratio.
			 * @param  {number} sceneX      The x position of the scene.
			 * @param  {number} sceneY      The y position of the scene.
			 * @param  {number} ratio       The zoom ratio of the scene.
			 * @param  {boolean} parseNodes Indicates if the nodes have to be parsed.
			 * @param  {boolean} parseEdges Indicates if the edges have to be parsed.
			 * @return {Graph} Returns itself.
			 */
			function translate(sceneX, sceneY, ratio, parseNodes, parseEdges) {
				var sizeRatio = Math.pow(ratio, self.p.nodesPowRatio);
				parseNodes && self.nodes.forEach(function(node) {
					if(!node['fixed']) {
						node['displayX'] = node['displayX'] * ratio + sceneX;
						node['displayY'] = node['displayY'] * ratio + sceneY;
					}

					node['displaySize'] = node['displaySize'] * sizeRatio;
				});

				sizeRatio = Math.pow(ratio, self.p.edgesPowRatio);
				parseEdges && self.edges.forEach(function(edge) {
					edge['displaySize'] = edge['displaySize'] * sizeRatio;
				});

				return self;
			};

			/**
			 * Determines the borders of the graph as it will be drawn. It is used to
			 * avoid the user to drag the graph out of the canvas.
			 */
			function setBorders() {
				self.borders = {};

				self.nodes.forEach(function(node) {
					self.borders.minX = Math.min(self.borders.minX == undefined ? node['displayX'] - node['displaySize'] : self.borders.minX, node['displayX'] - node['displaySize']);
					self.borders.maxX = Math.max(self.borders.maxX == undefined ? node['displayX'] + node['displaySize'] : self.borders.maxX, node['displayX'] + node['displaySize']);
					self.borders.minY = Math.min(self.borders.minY == undefined ? node['displayY'] - node['displaySize'] : self.borders.minY, node['displayY'] - node['displaySize']);
					self.borders.maxY = Math.max(self.borders.maxY == undefined ? node['displayY'] - node['displaySize'] : self.borders.maxY, node['displayY'] - node['displaySize']);
				});
			}

			/**
			 * Checks which nodes are under the (mX, mY) points, representing the mouse position.
			 * @param  {number} mX The mouse X position.
			 * @param  {number} mY The mouse Y position.
			 * @return {Graph} Returns itself.
			 */
			function checkHover(mX, mY) {
				var dX, dY, s, over = [], out = [];
				self.nodes.forEach(function(node) {
					if(node['hidden']) {
						node['hover'] = false;
						return;
					}

					dX = Math.abs(node['displayX'] - mX);
					dY = Math.abs(node['displayY'] - mY);
					s = node['displaySize'];

					var oldH = node['hover'];
					var newH = dX < s && dY < s && Math.sqrt(dX * dX + dY * dY) < s;

					if(oldH && !newH) {
						node['hover'] = false;
						out.push(node.id);
					} else if(newH && !oldH) {
						node['hover'] = true;
						over.push(node.id);
					}
				});

				over.length && self.dispatch('overnodes', over);
				out.length && self.dispatch('outnodes', out);

				return self;
			};

			/**
			 * Applies a function to a clone of each node (or indicated nodes), and then
			 * tries to apply the modifications made on the clones to the original nodes.
			 * @param  {function(Object)} fun The function to execute.
			 * @param  {?Array.<string>} ids  An Array of node IDs (optional).
			 * @return {Graph} Returns itself.
			 */
			function iterNodes(fun, ids) {
				var a = ids ? ids.map(function(id) {
					return self.nodesIndex[id];
				}) : self.nodes;

				var aCopies = a.map(cloneNode);
				aCopies.forEach(fun);

				a.forEach(function(n, i) {
					checkNode(n, aCopies[i]);
				});

				return self;
			};

			/**
			 * Applies a function to a clone of each edge (or indicated edges), and then
			 * tries to apply the modifications made on the clones to the original edges.
			 * @param  {function(Object)} fun The function to execute.
			 * @param  {?Array.<string>} ids  An Array of edge IDs (optional).
			 * @return {Graph} Returns itself.
			 */
			function iterEdges(fun, ids) {
				var a = ids ? ids.map(function(id) {
					return self.edgesIndex[id];
				}) : self.edges;

				var aCopies = a.map(cloneEdge);
				aCopies.forEach(fun);

				a.forEach(function(e, i) {
					checkEdge(e, aCopies[i]);
				});

				return self;
			};

			/**
			 * Returns a specific node clone or an array of specified node clones.
			 * @param  {(string|Array.<string>)} ids The ID or an array of node IDs.
			 * @return {(Object|Array.<Object>)} The clone or the array of clones.
			 */
			function getNodes(ids) {
				var a = (( ids instanceof Array ? ids : [ids]) || []).map(function(id) {
					return cloneNode(self.nodesIndex[id]);
				});

				return ( ids instanceof Array ? a : a[0]);
			};

			/**
			 * Returns a specific edge clone or an array of specified edge clones.
			 * @param  {(string|Array.<string>)} ids The ID or an array of edge IDs.
			 * @return {(Object|Array.<Object>)} The clone or the array of clones.
			 */
			function getEdges(ids) {
				var a = (( ids instanceof Array ? ids : [ids]) || []).map(function(id) {
					return cloneEdge(self.edgesIndex[id]);
				});

				return ( ids instanceof Array ? a : a[0]);
			};

			empty();

			this.addNode = addNode;
			this.addEdge = addEdge;
			this.dropNode = dropNode;
			this.dropEdge = dropEdge;

			this.iterEdges = iterEdges;
			this.iterNodes = iterNodes;

			this.getEdges = getEdges;
			this.getNodes = getNodes;

			this.empty = empty;
			this.rescale = rescale;
			this.translate = translate;
			this.setBorders = setBorders;
			this.checkHover = checkHover;
		}

		////////////////////////////////
		/**
		 * A class to monitor some local / global probes directly on an instance,
		 * inside a div DOM element.
		 * It executes different methods (called "probes") regularly, and displays
		 * the results on the element.
		 * @constructor
		 * @extends sigma.classes.Cascade
		 * @param {Sigma} instance The instance to monitor.
		 * @param {element} dom    The div DOM element to draw write on.
		 * @this {Monitor}
		 */
		function Monitor(instance, dom) {
			sigma.classes.Cascade.call(this);

			/**
			 * Represents "this", without the well-known scope issue.
			 * @private
			 * @type {Monitor}
			 */
			var self = this;

			/**
			 * {@link Sigma} instance owning this Monitor instance.
			 * @type {Sigma}
			 */
			this.instance = instance;

			/**
			 * Determines if the monitoring is activated or not.
			 * @type {Boolean}
			 */
			this.monitoring = false;

			/**
			 * The different parameters that define how this instance should work. It
			 * also contains the different probes.
			 * @see sigma.classes.Cascade
			 * @type {Object}
			 */
			this.p = {
				fps : 40,
				dom : dom,
				globalProbes : {
					'Time (ms)' : sigma.chronos.getExecutionTime,
					'Queue' : sigma.chronos.getQueuedTasksCount,
					'Tasks' : sigma.chronos.getTasksCount,
					'FPS' : sigma.chronos.getFPS
				},
				localProbes : {
					'Nodes count' : function() {
						return self.instance.graph.nodes.length;
					},
					'Edges count' : function() {
						return self.instance.graph.edges.length;
					}
				}
			};

			/**
			 * Activates the monitoring: Some texts describing some values about sigma.js
			 * or the owning {@link Sigma} instance will appear over the graph, but
			 * beneath the mouse sensible DOM element.
			 * @return {Monitor} Returns itself.
			 */
			function activate() {
				if(!self.monitoring) {
					self.monitoring = window.setInterval(routine, 1000 / self.p.fps);
				}

				return self;
			}

			/**
			 * Desactivates the monitoring: Will disappear, and stop computing the
			 * different probes.
			 * @return {Monitor} Returns itself.
			 */
			function desactivate() {
				if(self.monitoring) {
					window.clearInterval(self.monitoring);
					self.monitoring = null;

					self.p.dom.innerHTML = '';
				}

				return self;
			}

			/**
			 * The private method dedicated to compute the different values to observe.
			 * @private
			 * @return {Monitor} Returns itself.
			 */
			function routine() {
				var s = '';

				s += '<p>GLOBAL :</p>';
				for(var k in self.p.globalProbes) {
					s += '<p>' + k + ' : ' + self.p.globalProbes[k]() + '</p>';
				}

				s += '<br><p>LOCAL :</p>';
				for(var k in self.p.localProbes) {
					s += '<p>' + k + ' : ' + self.p.localProbes[k]() + '</p>';
				}

				self.p.dom.innerHTML = s;

				return self;
			}


			this.activate = activate;
			this.desactivate = desactivate;
		}

		///////////////////////////////////
		/**
		 * This class listen to all the different mouse events, to normalize them and
		 * dispatch action events instead (from "startinterpolate" to "isdragging",
		 * etc).
		 * @constructor
		 * @extends sigma.classes.Cascade
		 * @extends sigma.classes.EventDispatcher
		 * @param {element} dom The DOM element to bind the handlers on.
		 * @this {MouseCaptor}
		 */
		function MouseCaptor(dom) {
			sigma.classes.Cascade.call(this);
			sigma.classes.EventDispatcher.call(this);

			/**
			 * Represents "this", without the well-known scope issue.
			 * @private
			 * @type {MouseCaptor}
			 */
			var self = this;

			/**
			 * The DOM element to bind the handlers on.
			 * @type {element}
			 */
			var dom = dom;

			/**
			 * The different parameters that define how this instance should work.
			 * @see sigma.classes.Cascade
			 * @type {Object}
			 */
			this.p = {
				minRatio : 1,
				maxRatio : 32,
				marginRatio : 1,
				zoomDelta : 0.1,
				dragDelta : 0.3,
				zoomMultiply : 2,
				directZooming : false,
				blockScroll : true,
				inertia : 1.1,
				mouseEnabled : true
			};

			var oldMouseX = 0;
			var oldMouseY = 0;
			var startX = 0;
			var startY = 0;

			var oldStageX = 0;
			var oldStageY = 0;
			var oldRatio = 1;

			var targetRatio = 1;
			var targetStageX = 0;
			var targetStageY = 0;

			var lastStageX = 0;
			var lastStageX2 = 0;
			var lastStageY = 0;
			var lastStageY2 = 0;

			var progress = 0;
			var isZooming = false;

			this.stageX = 0;
			this.stageY = 0;
			this.ratio = 1;

			this.mouseX = 0;
			this.mouseY = 0;

			this.isMouseDown = false;

			/**
			 * Extract the local X position from a mouse event.
			 * @private
			 * @param  {event} e A mouse event.
			 * @return {number} The local X value of the mouse.
			 */
			function getX(e) {
				return e.offsetX != undefined && e.offsetX || e.layerX != undefined && e.layerX || e.clientX != undefined && e.clientX;
			};

			/**
			 * Extract the local Y position from a mouse event.
			 * @private
			 * @param  {event} e A mouse event.
			 * @return {number} The local Y value of the mouse.
			 */
			function getY(e) {
				return e.offsetY != undefined && e.offsetY || e.layerY != undefined && e.layerY || e.clientY != undefined && e.clientY;
			};

			/**
			 * Extract the wheel delta from a mouse event.
			 * @private
			 * @param  {event} e A mouse event.
			 * @return {number} The wheel delta of the mouse.
			 */
			function getDelta(e) {
				return e.wheelDelta != undefined && e.wheelDelta || e.detail != undefined && -e.detail;
			};

			/**
			 * The handler listening to the 'move' mouse event. It will set the mouseX
			 * and mouseY values as the mouse position values, prevent the default event,
			 * and dispatch a 'move' event.
			 * @private
			 * @param  {event} event A 'move' mouse event.
			 */
			function moveHandler(event) {
				oldMouseX = self.mouseX;
				oldMouseY = self.mouseY;

				self.mouseX = getX(event);
				self.mouseY = getY(event);

				self.isMouseDown && drag(event);
				self.dispatch('move');

				if(event.preventDefault) {
					event.preventDefault();
				} else {
					event.returnValue = false;
				}
			};

			/**
			 * The handler listening to the 'up' mouse event. It will set the isMouseDown
			 * value as false, dispatch a 'mouseup' event, and trigger stopDrag().
			 * @private
			 * @param  {event} event A 'up' mouse event.
			 */
			function upHandler(event) {
				if(self.p.mouseEnabled && self.isMouseDown) {
					self.isMouseDown = false;
					self.dispatch('mouseup');
					stopDrag();

					if(event.preventDefault) {
						event.preventDefault();
					} else {
						event.returnValue = false;
					}
				}
			};

			/**
			 * The handler listening to the 'down' mouse event. It will set the
			 * isMouseDown value as true, dispatch a 'mousedown' event, and trigger
			 * startDrag().
			 * @private
			 * @param  {event} event A 'down' mouse event.
			 */
			function downHandler(event) {
				if(self.p.mouseEnabled) {
					self.isMouseDown = true;
					oldMouseX = self.mouseX;
					oldMouseY = self.mouseY;

					self.dispatch('mousedown');

					startDrag();

					if(event.preventDefault) {
						event.preventDefault();
					} else {
						event.returnValue = false;
					}
				}
			};

			/**
			 * The handler listening to the 'wheel' mouse event. It will trigger
			 * {@link startInterpolate} with the event delta as parameter.
			 * @private
			 * @param  {event} event A 'wheel' mouse event.
			 */
			function wheelHandler(event) {
				if(self.p.mouseEnabled) {
					startInterpolate(self.mouseX, self.mouseY, self.ratio * (getDelta(event) > 0 ? self.p.zoomMultiply : 1 / self.p.zoomMultiply));

					if(self.p['blockScroll']) {
						if(event.preventDefault) {
							event.preventDefault();
						} else {
							event.returnValue = false;
						}
					}
				}
			};

			/**
			 * Will start computing the scene X and Y, until {@link stopDrag} is
			 * triggered.
			 */
			function startDrag() {
				oldStageX = self.stageX;
				oldStageY = self.stageY;
				startX = self.mouseX;
				startY = self.mouseY;

				lastStageX = self.stageX;
				lastStageX2 = self.stageX;
				lastStageY = self.stageY;
				lastStageY2 = self.stageY;

				self.dispatch('startdrag');
			};

			/**
			 * Stops computing the scene position.
			 */
			function stopDrag() {
				if(oldStageX != self.stageX || oldStageY != self.stageY) {
					startInterpolate(self.stageX + self.p.inertia * (self.stageX - lastStageX2), self.stageY + self.p.inertia * (self.stageY - lastStageY2));
				}
			};

			/**
			 * Computes the position of the scene, relatively to the mouse position, and
			 * dispatches a "drag" event.
			 */
			function drag() {
				var newStageX = self.mouseX - startX + oldStageX;
				var newStageY = self.mouseY - startY + oldStageY;

				if(newStageX != self.stageX || newStageY != self.stageY) {
					lastStageX2 = lastStageX;
					lastStageY2 = lastStageY;

					lastStageX = newStageX;
					lastStageY = newStageY;

					self.stageX = newStageX;
					self.stageY = newStageY;
					self.dispatch('drag');
				}
			};

			/**
			 * Will start computing the scene zoom ratio, until {@link stopInterpolate} is
			 * triggered.
			 * @param {number} x     The new stage X.
			 * @param {number} y     The new stage Y.
			 * @param {number} ratio The new zoom ratio.
			 */
			function startInterpolate(x, y, ratio) {
				if(self.isMouseDown) {
					return;
				}

				window.clearInterval(self.interpolationID);
				isZooming = ratio != undefined;

				oldStageX = self.stageX;
				targetStageX = x;

				oldStageY = self.stageY;
				targetStageY = y;

				oldRatio = self.ratio;
				targetRatio = ratio || self.ratio;
				targetRatio = Math.min(Math.max(targetRatio, self.p.minRatio), self.p.maxRatio);

				progress = self.p.directZooming ? 1 - ( isZooming ? self.p.zoomDelta : self.p.dragDelta) : 0;

				if(self.ratio != targetRatio || self.stageX != targetStageX || self.stageY != targetStageY) {
					interpolate();
					self.interpolationID = window.setInterval(interpolate, 50);
					self.dispatch('startinterpolate');
				}
			};

			/**
			 * Stops the move interpolation.
			 */
			function stopInterpolate() {
				var oldRatio = self.ratio;
				if(isZooming) {
					self.ratio = targetRatio;
					self.stageX = targetStageX + (self.stageX - targetStageX) * self.ratio / oldRatio;
					self.stageY = targetStageY + (self.stageY - targetStageY) * self.ratio / oldRatio;
				} else {
					self.stageX = targetStageX;
					self.stageY = targetStageY;
				}

				self.dispatch('stopinterpolate');
			};

			/**
			 * Computes the interpolate ratio and the position of the scene, relatively
			 * to the last mouse event delta received, and dispatches a "interpolate"
			 * event.
			 */
			function interpolate() {
				progress += ( isZooming ? self.p.zoomDelta : self.p.dragDelta);
				progress = Math.min(progress, 1);

				var k = sigma.easing.quadratic.easeout(progress);

				var oldRatio = self.ratio;

				self.ratio = oldRatio * (1 - k) + targetRatio * k;

				if(isZooming) {
					self.stageX = targetStageX + (self.stageX - targetStageX) * self.ratio / oldRatio;
					self.stageY = targetStageY + (self.stageY - targetStageY) * self.ratio / oldRatio;
				} else {
					self.stageX = oldStageX * (1 - k) + targetStageX * k;
					self.stageY = oldStageY * (1 - k) + targetStageY * k;
				}

				self.dispatch('interpolate');
				if(progress >= 1) {
					window.clearInterval(self.interpolationID);
					stopInterpolate();
				}
			};

			/**
			 * Checks that there is always a part of the graph that is displayed, to
			 * avoid the user to drag the graph out of the stage.
			 * @param  {Object} b      An object containing the borders of the graph.
			 * @param  {number} width  The width of the stage.
			 * @param  {number} height The height of the stage.
			 * @return {MouseCaptor} Returns itself.
			 */
			function checkBorders(b, width, height) {
				// TODO : Find the good formula
				/*if (!isNaN(b.minX) && !isNaN(b.maxX)) {
				 self.stageX = Math.min(
				 self.stageX = Math.max(
				 self.stageX,
				 (b.minX - width) * self.ratio +
				 self.p.marginRatio*(b.maxX - b.minX)
				 ),
				 (b.maxX - width) * self.ratio +
				 width -
				 self.p.marginRatio*(b.maxX - b.minX)
				 );
				 }

				 if (!isNaN(b.minY) && !isNaN(b.maxY)) {
				 self.stageY = Math.min(
				 self.stageY = Math.max(
				 self.stageY,
				 (b.minY - height) * self.ratio +
				 self.p.marginRatio*(b.maxY - b.minY)
				 ),
				 (b.maxY - height) * self.ratio +
				 height -
				 self.p.marginRatio*(b.maxY - b.minY)
				 );
				 }*/

				return self;
			};

			// ADD CALLBACKS
			dom.addEventListener('DOMMouseScroll', wheelHandler, true);
			dom.addEventListener('mousewheel', wheelHandler, true);
			dom.addEventListener('mousemove', moveHandler, true);
			dom.addEventListener('mousedown', downHandler, true);
			document.addEventListener('mouseup', upHandler, true);

			this.checkBorders = checkBorders;
			this.interpolate = startInterpolate;
		}

		////////////////////////////////////////////
		/**
		 * This class draws the graph on the different canvas DOM elements. It just
		 * contains all the different methods to draw the graph, synchronously or pseudo-asynchronously.
		 * @constructor
		 * @param {CanvasRenderingContext2D} nodesCtx  Context dedicated to draw nodes.
		 * @param {CanvasRenderingContext2D} edgesCtx  Context dedicated to draw edges.
		 * @param {CanvasRenderingContext2D} labelsCtx Context dedicated to draw labels.
		 * @param {CanvasRenderingContext2D} hoverCtx  Context dedicated to draw hover nodes labels.
		 * @param {Graph} graph                        A reference to the graph to draw.
		 * @param {number} w                           The width of the DOM root element.
		 * @param {number} h                           The width of the DOM root element.
		 * @extends sigma.classes.Cascade
		 * @this {Plotter}
		 */
		function Plotter(nodesCtx, edgesCtx, labelsCtx, hoverCtx, graph, w, h) {
			sigma.classes.Cascade.call(this);

			/**
			 * Represents "this", without the well-known scope issue.
			 * @private
			 * @type {Plotter}
			 */
			var self = this;

			/**
			 * The different parameters that define how this instance should work.
			 * @see sigma.classes.Cascade
			 * @type {Object}
			 */
			this.p = {
				// -------
				// LABELS:
				// -------
				//   Label color:
				//   - 'node'
				//   - default (then defaultLabelColor will be used instead)
				labelColor : 'default',
				defaultLabelColor : '#000',
				//   Label hover background color:
				//   - 'node'
				//   - default (then defaultHoverLabelBGColor will be used instead)
				labelHoverBGColor : 'default',
				defaultHoverLabelBGColor : '#fff',
				//   Label hover shadow:
				labelHoverShadow : true,
				labelHoverShadowColor : '#000',
				//   Label hover color:
				//   - 'node'
				//   - default (then defaultLabelHoverColor will be used instead)
				labelHoverColor : 'default',
				defaultLabelHoverColor : '#000',
				//   Label active background color:
				//   - 'node'
				//   - default (then defaultActiveLabelBGColor will be used instead)
				labelActiveBGColor : 'default',
				defaultActiveLabelBGColor : '#fff',
				//   Label active shadow:
				labelActiveShadow : true,
				labelActiveShadowColor : '#000',
				//   Label active color:
				//   - 'node'
				//   - default (then defaultLabelActiveColor will be used instead)
				labelActiveColor : 'default',
				defaultLabelActiveColor : '#000',
				//   Label size:
				//   - 'fixed'
				//   - 'proportional'
				//   Label size:
				//   - 'fixed'
				//   - 'proportional'
				labelSize : 'fixed',
				defaultLabelSize : 12, // for fixed display only
				labelSizeRatio : 2, // for proportional display only
				labelThreshold : 6,
				font : 'Arial',
				hoverFont : '',
				activeFont : '',
				fontStyle : '',
				hoverFontStyle : '',
				activeFontStyle : '',
				// ------
				// EDGES:
				// ------
				//   Edge color:
				//   - 'source'
				//   - 'target'
				//   - default (then defaultEdgeColor or edge['color'] will be used instead)
				edgeColor : 'source',
				defaultEdgeColor : '#aaa',
				defaultEdgeType : 'line',
				// ------
				// NODES:
				// ------
				defaultNodeColor : '#aaa',
				// HOVER:
				//   Node hover color:
				//   - 'node'
				//   - default (then defaultNodeHoverColor will be used instead)
				nodeHoverColor : 'node',
				defaultNodeHoverColor : '#fff',
				// ACTIVE:
				//   Node active color:
				//   - 'node'
				//   - default (then defaultNodeActiveColor will be used instead)
				nodeActiveColor : 'node',
				defaultNodeActiveColor : '#fff',
				//   Node border color:
				//   - 'node'
				//   - default (then defaultNodeBorderColor will be used instead)
				borderSize : 0,
				nodeBorderColor : 'node',
				defaultNodeBorderColor : '#fff',
				// --------
				// PROCESS:
				// --------
				edgesSpeed : 200,
				nodesSpeed : 200,
				labelsSpeed : 200
			};

			/**
			 * The canvas context dedicated to draw the nodes.
			 * @type {CanvasRenderingContext2D}
			 */
			var nodesCtx = nodesCtx;

			/**
			 * The canvas context dedicated to draw the edges.
			 * @type {CanvasRenderingContext2D}
			 */
			var edgesCtx = edgesCtx;

			/**
			 * The canvas context dedicated to draw the labels.
			 * @type {CanvasRenderingContext2D}
			 */
			var labelsCtx = labelsCtx;

			/**
			 * The canvas context dedicated to draw the hover nodes.
			 * @type {CanvasRenderingContext2D}
			 */
			var hoverCtx = hoverCtx;

			/**
			 * A reference to the graph to draw.
			 * @type {Graph}
			 */
			var graph = graph;

			/**
			 * The width of the stage to draw on.
			 * @type {number}
			 */
			var width = w;

			/**
			 * The height of the stage to draw on.
			 * @type {number}
			 */
			var height = h;

			/**
			 * The index of the next edge to draw.
			 * @type {number}
			 */
			this.currentEdgeIndex = 0;

			/**
			 * The index of the next node to draw.
			 * @type {number}
			 */
			this.currentNodeIndex = 0;

			/**
			 * The index of the next label to draw.
			 * @type {number}
			 */
			this.currentLabelIndex = 0;

			/**
			 * An atomic function to drawn the N next edges, with N as edgesSpeed.
			 * The counter is {@link this.currentEdgeIndex}.
			 * This function has been designed to work with {@link sigma.chronos}, that
			 * will insert frames at the middle of the calls, to make the edges drawing
			 * process fluid for the user.
			 * @see sigma.chronos
			 * @return {boolean} Returns true if all the edges are drawn and false else.
			 */
			function task_drawEdge() {
				var c = graph.edges.length;
				var s, t, i = 0;

				while(i++ < self.p.edgesSpeed && self.currentEdgeIndex < c) {
					e = graph.edges[self.currentEdgeIndex];
					s = e['source'];
					t = e['target'];
					if(e['hidden'] || s['hidden'] || t['hidden'] || (!self.isOnScreen(s) && !self.isOnScreen(t))) {
						self.currentEdgeIndex++;
					} else {
						drawEdge(graph.edges[self.currentEdgeIndex++]);
					}
				}

				return self.currentEdgeIndex < c;
			};

			/**
			 * An atomic function to drawn the N next nodes, with N as nodesSpeed.
			 * The counter is {@link this.currentEdgeIndex}.
			 * This function has been designed to work with {@link sigma.chronos}, that
			 * will insert frames at the middle of the calls, to make the nodes drawing
			 * process fluid for the user.
			 * @see sigma.chronos
			 * @return {boolean} Returns true if all the nodes are drawn and false else.
			 */
			function task_drawNode() {
				var c = graph.nodes.length;
				var i = 0;

				while(i++ < self.p.nodesSpeed && self.currentNodeIndex < c) {
					if(!self.isOnScreen(graph.nodes[self.currentNodeIndex])) {
						self.currentNodeIndex++;
					} else {
						drawNode(graph.nodes[self.currentNodeIndex++]);
					}
				}

				return self.currentNodeIndex < c;
			};

			/**
			 * An atomic function to drawn the N next labels, with N as labelsSpeed.
			 * The counter is {@link this.currentEdgeIndex}.
			 * This function has been designed to work with {@link sigma.chronos}, that
			 * will insert frames at the middle of the calls, to make the labels drawing
			 * process fluid for the user.
			 * @see sigma.chronos
			 * @return {boolean} Returns true if all the labels are drawn and false else.
			 */
			function task_drawLabel() {
				var c = graph.nodes.length;
				var i = 0;

				while(i++ < self.p.labelsSpeed && self.currentLabelIndex < c) {
					if(!self.isOnScreen(graph.nodes[self.currentLabelIndex])) {
						self.currentLabelIndex++;
					} else {
						drawLabel(graph.nodes[self.currentLabelIndex++]);
					}
				}

				return self.currentLabelIndex < c;
			};

			/**
			 * Draws one node to the corresponding canvas.
			 * @param  {Object} node The node to draw.
			 * @return {Plotter} Returns itself.
			 */
			function drawNode(node) {
				var size = Math.round(node['displaySize'] * 10) / 10;

				//zhang.youlei add
				var ctx = nodesCtx;

				var grd = ctx.createRadialGradient(node['displayX'], node['displayY'], size, node['displayX'], node['displayY'], size * 3);
				grd.addColorStop(0, node['color']);
				grd.addColorStop(1, "#333333");
				ctx.fillStyle = grd;
				//ctx.fillRect(node['displayX']-15,node['displayY']-15,30,30);
				ctx.beginPath();
				ctx.arc(node['displayX'], node['displayY'], size * 3, 0, Math.PI * 2, true);
				ctx.closePath();
				ctx.fill();

				ctx.fillStyle = node['color'];
				ctx.beginPath();
				ctx.arc(node['displayX'], node['displayY'], size, 0, Math.PI * 2, true);
				ctx.closePath();
				ctx.fill();

				node['hover'] && drawHoverNode(node);
				return self;
			};

			/**
			 * Draws one edge to the corresponding canvas.
			 * @param  {Object} edge The edge to draw.
			 * @return {Plotter} Returns itself.
			 */
			function drawEdge(edge) {
				var x1 = edge['source']['displayX'];
				var y1 = edge['source']['displayY'];
				var x2 = edge['target']['displayX'];
				var y2 = edge['target']['displayY'];
				var color = edge['color'];

				if(!color) {
					switch (self.p.edgeColor) {
						case 'source':
							color = edge['source']['color'] || self.p.defaultNodeColor;
							break;
						case 'target':
							color = edge['target']['color'] || self.p.defaultNodeColor;
							break;
						default:
							color = self.p.defaultEdgeColor;
							break;
					}
				}

				var ctx = edgesCtx;

				switch (edge['type'] || self.p.defaultEdgeType) {
					case 'curve':
						ctx.strokeStyle = color;
						ctx.lineWidth = edge['displaySize'] / 3;
						ctx.beginPath();
						ctx.moveTo(x1, y1);
						ctx.quadraticCurveTo((x1 + x2) / 2 + (y2 - y1) / 4, (y1 + y2) / 2 + (x1 - x2) / 4, x2, y2);
						ctx.stroke();
						break;
					case 'line':
					default:
						ctx.strokeStyle = color;
						ctx.lineWidth = edge['displaySize'] / 3;
						ctx.beginPath();
						ctx.moveTo(x1, y1);
						ctx.lineTo(x2, y2);

						ctx.stroke();
						break;
				}

				return self;
			};

			/**
			 * Draws one label to the corresponding canvas.
			 * @param  {Object} node The label to draw.
			 * @return {Plotter} Returns itself.
			 */
			function drawLabel(node) {
				var ctx = labelsCtx;

				if(node['displaySize'] >= self.p.labelThreshold || node['forceLabel']) {
					var fontSize = self.p.labelSize == 'fixed' ? self.p.defaultLabelSize : self.p.labelSizeRatio * node['displaySize'];

					ctx.font = self.p.fontStyle + fontSize + 'px ' + self.p.font;
					ctx.fillStyle = self.p.labelColor == 'node' ? (node['color'] || self.p.defaultNodeColor) : self.p.defaultLabelColor;
					//ctx.fillText(node['label'], Math.round(node['displayX'] + node['displaySize'] * 1.5), Math.round(node['displayY'] + fontSize / 2 - 3));
					//zhang.youlei@20120820 当某个节点超出预期，则显示字幕
					ctx.fillText(node['label'], Math.round(node['displayX'] - node['displaySize'] ), Math.round(node['displayY'] + fontSize / 2 - 3));
				}

				return self;
			};

			/**
			 * Draws one hover node to the corresponding canvas.
			 * @param  {Object} node The hover node to draw.
			 * @return {Plotter} Returns itself.
			 */
			function drawHoverNode(node) {
				var ctx = hoverCtx;
				var fontSize = self.p.labelSize == 'fixed' ? self.p.defaultLabelSize : self.p.labelSizeRatio * node['displaySize'];
				ctx.font = (self.p.hoverFontStyle || self.p.fontStyle || '') + ' ' + fontSize + 'px ' + (self.p.hoverFont || self.p.font || '');
				ctx.fillStyle = self.p.labelHoverBGColor == 'node' ? (node['color'] || self.p.defaultNodeColor) : self.p.defaultHoverLabelBGColor;

				// Label background:
				ctx.beginPath();

				if(self.p.labelHoverShadow) {
					ctx.shadowOffsetX = 0;
					ctx.shadowOffsetY = 0;
					ctx.shadowBlur = 4;
					ctx.shadowColor = self.p.labelHoverShadowColor;
				}

//zhang.youlei@20120820  ctx.measureText()
				sigma.tools.drawRoundRect(ctx, Math.round(node['displayX'] - fontSize / 2 - 2), Math.round(node['displayY'] - fontSize / 2 - 2), Math.round(ctx.measureText(node['label']).width + node['displaySize'] * 1.5 + fontSize / 2 + 4), Math.round(fontSize + 4), Math.round(fontSize / 2 + 2), 'left');
				ctx.closePath();
				ctx.fill();

				ctx.shadowOffsetX = 0;
				ctx.shadowOffsetY = 0;
				ctx.shadowBlur = 0;

				// Node border:
				ctx.beginPath();
				ctx.fillStyle = self.p.nodeBorderColor == 'node' ? (node['color'] || self.p.defaultNodeColor) : self.p.defaultNodeBorderColor;
				ctx.arc(Math.round(node['displayX']), Math.round(node['displayY']), node['displaySize'] + self.p.borderSize, 0, Math.PI * 2, true);
				ctx.closePath();
				ctx.fill();

				// Node:
				ctx.beginPath();
				ctx.fillStyle = self.p.nodeHoverColor == 'node' ? (node['color'] || self.p.defaultNodeColor) : self.p.defaultNodeHoverColor;
				ctx.arc(Math.round(node['displayX']), Math.round(node['displayY']), node['displaySize'], 0, Math.PI * 2, true);

				ctx.closePath();
				ctx.fill();

				// Label:
				ctx.fillStyle = self.p.labelHoverColor == 'node' ? (node['color'] || self.p.defaultNodeColor) : self.p.defaultLabelHoverColor;
				ctx.fillText(node['label'], Math.round(node['displayX'] + node['displaySize'] * 1.5), Math.round(node['displayY'] + fontSize / 2 - 3));

				return self;
			};

			/**
			 * Draws one active node to the corresponding canvas.
			 * @param  {Object} node The active node to draw.
			 * @return {Plotter} Returns itself.
			 */
			function drawActiveNode(node) {
				var ctx = hoverCtx;

				if(!isOnScreen(node)) {
					return self;
				}

				var fontSize = self.p.labelSize == 'fixed' ? self.p.defaultLabelSize : self.p.labelSizeRatio * node['displaySize'];
				ctx.font = (self.p.activeFontStyle || self.p.fontStyle || '') + ' ' + fontSize + 'px ' + (self.p.activeFont || self.p.font || '');
				ctx.fillStyle = self.p.labelHoverBGColor == 'node' ? (node['color'] || self.p.defaultNodeColor) : self.p.defaultActiveLabelBGColor;

				// Label background:
				ctx.beginPath();
				if(self.p.labelActiveShadow) {
					ctx.shadowOffsetX = 0;
					ctx.shadowOffsetY = 0;
					ctx.shadowBlur = 4;
					ctx.shadowColor = self.p.labelActiveShadowColor;
				}

				sigma.tools.drawRoundRect(ctx, Math.round(node['displayX'] - fontSize / 2 - 2), Math.round(node['displayY'] - fontSize / 2 - 2), Math.round(ctx.measureText(node['label']).width + node['displaySize'] * 1.5 + fontSize / 2 + 4), Math.round(fontSize + 4), Math.round(fontSize / 2 + 2), 'left');
				ctx.closePath();
				ctx.fill();

				ctx.shadowOffsetX = 0;
				ctx.shadowOffsetY = 0;
				ctx.shadowBlur = 0;

				// Node border:
				ctx.beginPath();
				ctx.fillStyle = self.p.nodeBorderColor == 'node' ? (node['color'] || self.p.defaultNodeColor) : self.p.defaultNodeBorderColor;
				ctx.arc(Math.round(node['displayX']), Math.round(node['displayY']), node['displaySize'] + self.p.borderSize, 0, Math.PI * 2, true);
				ctx.closePath();
				ctx.fill();

				// Node:
				ctx.beginPath();
				ctx.fillStyle = self.p.nodeActiveColor == 'node' ? (node['color'] || self.p.defaultNodeColor) : self.p.defaultNodeActiveColor;
				ctx.arc(Math.round(node['displayX']), Math.round(node['displayY']), node['displaySize'], 0, Math.PI * 2, true);

				ctx.closePath();
				ctx.fill();

				// Label:
				ctx.fillStyle = self.p.labelActiveColor == 'node' ? (node['color'] || self.p.defaultNodeColor) : self.p.defaultLabelActiveColor;
				ctx.fillText(node['label'], Math.round(node['displayX'] + node['displaySize'] * 1.5), Math.round(node['displayY'] + fontSize / 2 - 3));

				return self;
			};

			/**
			 * Determines if a node is on the screen or not. The limits here are
			 * bigger than the actual screen, to avoid seeing labels disappear during
			 * the graph manipulation.
			 * @param  {Object}  node The node to check if it is on or out the screen.
			 * @return {boolean} Returns false if the node is hidden or not on the screen
			 *                   or true else.
			 */
			function isOnScreen(node) {
				if(isNaN(node['x']) || isNaN(node['y'])) {
					throw (new Error('A node\'s coordinate is not a ' + 'number (id: ' + node['id'] + ')')
					);
				}

				return !node['hidden'] && (node['displayX'] + node['displaySize'] > -width / 3) && (node['displayX'] - node['displaySize'] < width * 4 / 3) && (node['displayY'] + node['displaySize'] > -height / 3) && (node['displayY'] - node['displaySize'] < height * 4 / 3);
			};

			/**
			 * Resizes this instance.
			 * @param  {number} w The new width.
			 * @param  {number} h The new height.
			 * @return {Plotter} Returns itself.
			 */
			function resize(w, h) {
				width = w;
				height = h;

				return self;
			}


			this.task_drawLabel = task_drawLabel;
			this.task_drawEdge = task_drawEdge;
			this.task_drawNode = task_drawNode;
			this.drawActiveNode = drawActiveNode;
			this.drawHoverNode = drawHoverNode;
			this.isOnScreen = isOnScreen;
			this.resize = resize;
		}

		////////////////////////////////////////////
		/**
		 * Sigma is the main class. It represents the core of any instance id sigma.js.
		 * It is private and can be initialized only from inside sigma.js. To see its
		 * public interface, see {@link SigmaPublic}.
		 * It owns its own {@link Graph}, {@link MouseCaptor}, {@link Plotter}
		 * and {@link Monitor}.
		 * @constructor
		 * @extends sigma.classes.Cascade
		 * @extends sigma.classes.EventDispatcher
		 * @param {element} root The DOM root of this instance (a div, for example).
		 * @param {string} id    The ID of this instance.
		 * @this {Sigma}
		 */
		function Sigma(root, id) {
			sigma.classes.Cascade.call(this);
			sigma.classes.EventDispatcher.call(this);

			/**
			 * Represents "this", without the well-known scope issue.
			 * @private
			 * @type {Sigma}
			 */
			var self = this;

			/**
			 * The ID of the instance.
			 * @type {string}
			 */
			this.id = id.toString();

			/**
			 * The different parameters that define how this instance should work.
			 * @see sigma.classes.Cascade
			 * @type {Object}
			 */
			this.p = {
				auto : true,
				drawNodes : 2,
				drawEdges : 1,
				drawLabels : 2,
				lastNodes : 2,
				lastEdges : 0,
				lastLabels : 2,
				drawHoverNodes : true,
				drawActiveNodes : true
			};

			/**
			 * The root DOM element of this instance, containing every other elements.
			 * @type {element}
			 */
			this.domRoot = root;

			/**
			 * The width of this instance - initially, the root's width.
			 * @type {number}
			 */
			this.width = this.domRoot.offsetWidth;

			/**
			 * The height of this instance - initially, the root's height.
			 * @type {number}
			 */
			this.height = this.domRoot.offsetHeight;

			/**
			 * The graph of this instance - initiallyempty.
			 * @type {Graph}
			 */
			this.graph = new Graph();

			/**
			 * An object referencing every DOM elements used by this instance.
			 * @type {Object}
			 */
			this.domElements = {};

			initDOM('edges', 'canvas');
			initDOM('nodes', 'canvas');
			initDOM('labels', 'canvas');
			initDOM('hover', 'canvas');
			initDOM('monitor', 'div');
			initDOM('mouse', 'canvas');

			/**
			 * The class dedicated to manage the drawing process of the graph of the
			 * different canvas.
			 * @type {Plotter}
			 */
			this.plotter = new Plotter(this.domElements.nodes.getContext('2d'), this.domElements.edges.getContext('2d'), this.domElements.labels.getContext('2d'), this.domElements.hover.getContext('2d'), this.graph, this.width, this.height);

			/**
			 * The class dedicated to monitor different probes about the running
			 * processes or the data, such as the number of nodes or edges, or how
			 * many times the graph is drawn per second.
			 * @type {Monitor}
			 */
			this.monitor = new Monitor(this, this.domElements.monitor);

			/**
			 * The class dedicated to manage the different mouse events.
			 * @type {MouseCaptor}
			 */
			this.mousecaptor = new MouseCaptor(this.domElements.mouse, this.id);

			// Interaction listeners:
			this.mousecaptor.bind('drag interpolate', function(e) {
				self.draw(self.p.auto ? 2 : self.p.drawNodes, self.p.auto ? 0 : self.p.drawEdges, self.p.auto ? 2 : self.p.drawLabels, true);
			}).bind('stopdrag stopinterpolate', function(e) {
				self.draw(self.p.auto ? 2 : self.p.drawNodes, self.p.auto ? 1 : self.p.drawEdges, self.p.auto ? 2 : self.p.drawLabels, true);
			}).bind('mousedown mouseup', function(e) {
				var targeted = self.graph.nodes.filter(function(n) {
					return !!n['hover'];
				}).map(function(n) {
					return n.id;
				});

				self.dispatch(e['type'] == 'mousedown' ? 'downgraph' : 'upgraph');

				if(targeted.length) {
					self.dispatch(e['type'] == 'mousedown' ? 'downnodes' : 'upnodes', targeted);
				}
			}).bind('move', function() {
				self.domElements.hover.getContext('2d').clearRect(0, 0, self.domElements.hover.width, self.domElements.hover.height);

				drawHover();
				drawActive();
			});

			sigma.chronos.bind('startgenerators', function() {
				if(sigma.chronos.getGeneratorsIDs().some(function(id) {
					return !!id.match(new RegExp('_ext_' + self.id + '$', ''));
				})) {
					self.draw(self.p.auto ? 2 : self.p.drawNodes, self.p.auto ? 0 : self.p.drawEdges, self.p.auto ? 2 : self.p.drawLabels);
				}
			}).bind('stopgenerators', function() {
				self.draw();
			});

			/**
			 * Resizes the element, and redraws the graph with the last settings.
			 * @param  {?number} w The new width (if undefined, it will use the root
			 *                     width).
			 * @param  {?number} h The new height (if undefined, it will use the root
			 *                     height).
			 * @return {Sigma} Returns itself.
			 */
			function resize(w, h) {
				var oldW = self.width, oldH = self.height;

				if(w != undefined && h != undefined) {
					self.width = w;
					self.height = h;
				} else {
					self.width = self.domRoot.offsetWidth;
					self.height = self.domRoot.offsetHeight;
				}

				if(oldW != self.width || oldH != self.height) {
					for(var k in self.domElements) {
						self.domElements[k].setAttribute('width', self.width + 'px');
						self.domElements[k].setAttribute('height', self.height + 'px');
					}
					self.plotter.resize(self.width, self.height);
					self.draw(self.p.lastNodes, self.p.lastEdges, self.p.lastLabels, true);
				}
				return self;
			};

			/**
			 * Kills every drawing task currently running. Basically, it stops this
			 * instance's drawing process.
			 * @return {Sigma} Returns itself.
			 */
			function clearSchedule() {
				sigma.chronos.removeTask('node_' + self.id, 2).removeTask('edge_' + self.id, 2).removeTask('label_' + self.id, 2).stopTasks();
				return self;
			};

			/**
			 * Initialize a DOM element, that will be stores by this instance, to make
			 * automatic these elements resizing.
			 * @private
			 * @param  {string} id   The element's ID.
			 * @param  {string} type The element's nodeName (Example : canvas, div, ...).
			 * @return {Sigma} Returns itself.
			 */
			function initDOM(id, type) {
				self.domElements[id] = document.createElement(type);
				self.domElements[id].style.position = 'absolute';
				self.domElements[id].setAttribute('id', 'sigma_' + id + '_' + self.id);
				self.domElements[id].setAttribute('class', 'sigma_' + id + '_' + type);
				self.domElements[id].setAttribute('width', self.width + 'px');
				self.domElements[id].setAttribute('height', self.height + 'px');

				self.domRoot.appendChild(self.domElements[id]);
				return self;
			};

			/**
			 * Starts the graph drawing process. The three first parameters indicate
			 * how the different layers have to be drawn:
			 * . -1: The layer is not drawn, but it is not erased.
			 * . 0:  The layer is not drawn.
			 * . 1:  The layer is drawn progressively.
			 * . 2:  The layer is drawn directly.
			 * @param  {?number} nodes  Determines if and how the nodes must be drawn.
			 * @param  {?number} edges  Determines if and how the edges must be drawn.
			 * @param  {?number} labels Determines if and how the labels must be drawn.
			 * @param  {?boolean} safe  If true, nothing will happen if any generator
			 *                          affiliated to this instance is currently running
			 *                          (an iterative layout, for example).
			 * @return {Sigma} Returns itself.
			 */
			function draw(nodes, edges, labels, safe) {
				if(safe && sigma.chronos.getGeneratorsIDs().some(function(id) {
					return !!id.match(new RegExp('_ext_' + self.id + '$', ''));
				})) {
					return self;
				}

				var n = (nodes == undefined) ? self.p.drawNodes : nodes;
				var e = (edges == undefined) ? self.p.drawEdges : edges;
				var l = (labels == undefined) ? self.p.drawLabels : labels;

				var params = {
					nodes : n,
					edges : e,
					labels : l
				};

				self.p.lastNodes = n;
				self.p.lastEdges = e;
				self.p.lastLabels = l;

				// Remove tasks:
				clearSchedule();

				// Rescale graph:
				self.graph.rescale(self.width, self.height, n > 0, e > 0).setBorders();
				self.mousecaptor.checkBorders(self.graph.borders, self.width, self.height);
				self.graph.translate(self.mousecaptor.stageX, self.mousecaptor.stageY, self.mousecaptor.ratio, n > 0, e > 0);
				self.dispatch('graphscaled');

				// Clear scene:
				for(var k in self.domElements) {
					if(self.domElements[k].nodeName.toLowerCase() == 'canvas' && (params[k] == undefined || params[k] >= 0)) {
						self.domElements[k].getContext('2d').clearRect(0, 0, self.domElements[k].width, self.domElements[k].height);
					}
				}

				self.plotter.currentEdgeIndex = 0;
				self.plotter.currentNodeIndex = 0;
				self.plotter.currentLabelIndex = 0;

				var previous = null;
				var start = false;

				if(n) {
					if(n > 1) {
						while(self.plotter.task_drawNode()) {
						}
					} else {
						sigma.chronos.addTask(self.plotter.task_drawNode, 'node_' + self.id, false);

						start = true;
						previous = 'node_' + self.id;
					}
				}

				if(l) {
					if(l > 1) {
						while(self.plotter.task_drawLabel()) {
						}
					} else {
						if(previous) {
							sigma.chronos.queueTask(self.plotter.task_drawLabel, 'label_' + self.id, previous);
						} else {
							sigma.chronos.addTask(self.plotter.task_drawLabel, 'label_' + self.id, false);
						}

						start = true;
						previous = 'label_' + self.id;
					}
				}

				if(e) {
					if(e > 1) {
						while(self.plotter.task_drawEdge()) {
						}
					} else {
						if(previous) {
							sigma.chronos.queueTask(self.plotter.task_drawEdge, 'edge_' + self.id, previous);
						} else {
							sigma.chronos.addTask(self.plotter.task_drawEdge, 'edge_' + self.id, false);
						}

						start = true;
						previous = 'edge_' + self.id;
					}
				}

				self.dispatch('draw');

				self.refresh();

				start && sigma.chronos.runTasks();
				return self;
			};

			/**
			 * Draws the hover and active nodes labels.
			 * @return {Sigma} Returns itself.
			 */
			function refresh() {
				self.domElements.hover.getContext('2d').clearRect(0, 0, self.domElements.hover.width, self.domElements.hover.height);

				drawHover();
				drawActive();

				return self;
			}

			/**
			 * Draws the hover nodes labels. This method is applied directly, and does
			 * not use the pseudo-asynchronous tasks process.
			 * @return {Sigma} Returns itself.
			 */
			function drawHover() {
				if(self.p.drawHoverNodes) {
					self.graph.checkHover(self.mousecaptor.mouseX, self.mousecaptor.mouseY);

					self.graph.nodes.forEach(function(node) {
						if(node.hover && !node.active) {
							self.plotter.drawHoverNode(node);
						}
					});
				}

				return self;
			}

			/**
			 * Draws the active nodes labels. This method is applied directly, and does
			 * not use the pseudo-asynchronous tasks process.
			 * @return {Sigma} Returns itself.
			 */
			function drawActive() {
				if(self.p.drawActiveNodes) {
					self.graph.nodes.forEach(function(node) {
						if(node.active) {
							self.plotter.drawActiveNode(node);
						}
					});
				}

				return self;
			}

			// Apply plugins:
			for(var i = 0; i < local.plugins.length; i++) {
				local.plugins[i](this);
			}

			this.draw = draw;
			this.resize = resize;
			this.refresh = refresh;
			this.drawHover = drawHover;
			this.drawActive = drawActive;
			this.clearSchedule = clearSchedule;

			window.addEventListener('resize', function() {
				self.resize();
			});
		}

		//////////////////sigmapublics///////////////////////
		function SigmaPublic(sigmaInstance) {
			var s = sigmaInstance;
			var self = this;
			sigma.classes.EventDispatcher.call(this);

			this._core = sigmaInstance;

			this.kill = function() {
				// TODO
			};

			this.getID = function() {
				return s.id;
			};

			// Config:
			this.configProperties = function(a1, a2) {
				var res = s.config(a1, a2);
				return res == s ? self : res;
			};

			this.drawingProperties = function(a1, a2) {
				var res = s.plotter.config(a1, a2);
				return res == s.plotter ? self : res;
			};

			this.mouseProperties = function(a1, a2) {
				var res = s.mousecaptor.config(a1, a2);
				return res == s.mousecaptor ? self : res;
			};

			this.graphProperties = function(a1, a2) {
				var res = s.graph.config(a1, a2);
				return res == s.graph ? self : res;
			};

			this.getMouse = function() {
				return {
					mouseX : s.mousecaptor.mouseX,
					mouseY : s.mousecaptor.mouseY,
					down : s.mousecaptor.isMouseDown
				};
			};

			// Actions:
			this.position = function(stageX, stageY, ratio) {
				if(arguments.length == 0) {
					return {
						stageX : s.mousecaptor.stageX,
						stageY : s.mousecaptor.stageY,
						ratio : s.mousecaptor.ratio
					};
				} else {
					s.mousecaptor.stageX = stageX != undefined ? stageX : s.mousecaptor.stageX;
					s.mousecaptor.stageY = stageY != undefined ? stageY : s.mousecaptor.stageY;
					s.mousecaptor.ratio = ratio != undefined ? ratio : s.mousecaptor.ratio;

					return self;
				}
			};

			this.goTo = function(stageX, stageY, ratio) {
				s.mousecaptor.interpolate(stageX, stageY, ratio);
				return self;
			};

			this.zoomTo = function(x, y, ratio) {
				ratio = Math.min(Math.max(s.mousecaptor.config('minRatio'), ratio), s.mousecaptor.config('maxRatio'));
				if(ratio == s.mousecaptor.ratio) {
					s.mousecaptor.interpolate(x - s.width / 2 + s.mousecaptor.stageX, y - s.height / 2 + s.mousecaptor.stageY);
				} else {
					s.mousecaptor.interpolate((ratio * x - s.mousecaptor.ratio * s.width / 2) / (ratio - s.mousecaptor.ratio), (ratio * y - s.mousecaptor.ratio * s.height / 2) / (ratio - s.mousecaptor.ratio), ratio);
				}
				return self;
			};

			this.resize = function(w, h) {
				s.resize(w, h);
				return self;
			};

			this.draw = function(nodes, edges, labels, safe) {
				s.draw(nodes, edges, labels, safe);
				return self;
			};

			this.refresh = function() {
				s.refresh();
				return self;
			};

			// Tasks methods:
			this.addGenerator = function(id, task, condition) {
				sigma.chronos.addGenerator(id + '_ext_' + s.id, task, condition);
				return self;
			};

			this.removeGenerator = function(id) {
				sigma.chronos.removeGenerator(id + '_ext_' + s.id);
				return self;
			};

			// Graph methods:
			this.addNode = function(id, params) {
				s.graph.addNode(id, params);
				return self;
			};

			this.addEdge = function(id, source, target, params) {
				s.graph.addEdge(id, source, target, params);
				return self;
			}

			this.dropNode = function(v) {
				s.graph.dropNode(v);
				return self;
			};

			this.dropEdge = function(v) {
				s.graph.dropEdge(v);
				return self;
			};

			this.pushGraph = function(object, safe) {
				object.nodes && object.nodes.forEach(function(node) {
					node['id'] && (!safe || !s.graph.nodesIndex[node['id']]) && self.addNode(node['id'], node);
				});

				var isEdgeValid;
				object.edges && object.edges.forEach(function(edge) {
					validID = edge['source'] && edge['target'] && edge['id'];
					validID && (!safe || !s.graph.edgesIndex[edge['id']]) && self.addNode(edge['id'], edge['source'], edge['target'], edge);
				});

				return self;
			};

			this.emptyGraph = function() {
				s.graph.empty();
				return self;
			};

			this.getNodesCount = function() {
				return s.graph.nodes.length;
			};

			this.getEdgesCount = function() {
				return s.graph.edges.length;
			};

			this.iterNodes = function(fun, ids) {
				s.graph.iterNodes(fun, ids);
				return self;
			};

			this.iterEdges = function(fun, ids) {
				s.graph.iterEdges(fun, ids);
				return self;
			};

			this.getNodes = function(ids) {
				return s.graph.getNodes(ids);
			};

			this.getEdges = function(ids) {
				return s.graph.getEdges(ids);
			};

			// Monitoring
			this.activateMonitoring = function() {
				return s.monitor.activate();
			};

			this.desactivateMonitoring = function() {
				return s.monitor.desactivate();
			};

			// Events
			s.bind('downnodes upnodes downgraph upgraph', function(e) {
				self.dispatch(e.type, e.content);
			});

			s.graph.bind('overnodes outnodes', function(e) {
				self.dispatch(e.type, e.content);
			});
		}

		////////////////////addplugins.js//////////////////

		/**
		 * Add a function to the prototype of SigmaPublic, but with access to the
		 * Sigma class properties.
		 * @param {string} pluginName        [description].
		 * @param {function} caller          [description].
		 * @param {function(Sigma)} launcher [description].
		 */
		sigma.addPlugin = function(pluginName, caller, launcher) {
			SigmaPublic.prototype[pluginName] = caller;
			local.plugins.push(launcher);
		};
		
		/////////////////////////chronos////////////////////////
		/**
		 * sigma.chronos manages frames insertion to simulate asynchronous computing.
		 * It has been designed to make possible to execute heavy computing tasks
		 * for the browser, without freezing it.
		 * @constructor
		 * @extends sigma.classes.Cascade
		 * @extends sigma.classes.EventDispatcher
		 * @this {sigma.chronos}
		 */
		sigma.chronos = new (function() {
			sigma.classes.EventDispatcher.call(this);
	
			/**
			* Represents "this", without the well-known scope issue.
			* @private
			* @type {sigma.chronos}
			*/
			var self = this;
	
			/**
			* Indicates whether any task is actively running or not.
			* @private
			* @type {boolean}
			*/
			var isRunning = false;

			/**
			* Indicates the FPS "goal", that will define the theoretical
			* frame length.
			* @private
			* @type {number}
			*/
			var fpsReq = 80;

			/**
			* Stores the last computed FPS value (FPS is computed only when any
			* task is running).
			* @private
			* @type {number}
			*/
			var lastFPS = 0;
	
			/**
			* The number of frames inserted since the last start.
			* @private
			* @type {number}
			*/
			var framesCount = 0;

			/**
			* The theoretical frame time.
			* @private
			* @type {number}
			*/
			var frameTime = 1000 / fpsReq;

			/**
			* The theoretical frame length, minus the last measured delay.
			* @private
			* @type {number}
			*/
			var correctedFrameTime = frameTime;

			/**
			* The measured length of the last frame.
			* @private
			* @type {number}
			*/
			var effectiveTime = 0;
	
			/**
			* The time passed since the last runTasks action.
			* @private
			* @type {number}
			*/
			var currentTime = 0;

			/**
			* The time when the last frame was inserted.
			* @private
			* @type {number}
			*/
			var startTime = 0;
	
			/**
			* The difference between the theoretical frame length and the
			* last measured frame length.
			* @private
			* @type {number}
			*/
			var delay = 0;

			/**
			* The container of all active generators.
			* @private
			* @type {Object.<string, Object>}
			*/
			var generators = {};
	
			/**
			* The array of all the referenced and active tasks.
			* @private
			* @type {Array.<Object>}
			*/
			var tasks = [];
	
			/**
			* The array of all the referenced and queued tasks.
			* @private
			* @type {Array.<Object>}
			*/
			var queuedTasks = [];

			/**
			* The index of the next task to execute.
			* @private
			* @type {number}
			*/
			var taskIndex = 0;
	
			/**
			* Inserts a frame before executing the callback.
			* @param  {function()} callback The callback to execute after having
			*                               inserted the frame.
			* @return {sigma.chronos} Returns itself.
			*/
			function insertFrame(callback) {
				window.setTimeout(callback, 0);
				return self;
			}

			/**
			* The local method that executes routine, and inserts frames when needed.
			* It dispatches a "frameinserted" event after having inserted any frame,
			* and an "insertframe" event before.
			* @private
			*/
			function frameInserter() {
				self.dispatch('frameinserted');
				while (isRunning && tasks.length && routine()) {}
	
				if (!isRunning || !tasks.length) {
					stopTasks();
				} else {
					startTime = (new Date()).getTime();
					framesCount++;
					delay = effectiveTime - frameTime;
					correctedFrameTime = frameTime - delay;
			
					self.dispatch('insertframe');
					insertFrame(frameInserter);
				}
			};

			/**
			* The local method that executes the tasks, and compares the current frame
			* length to the ideal frame length.
			* @private
			* @return {boolean} Returns false if the current frame should be ended,
			*                   and true else.
			*/
			function routine() {
				taskIndex = taskIndex % tasks.length;
				
				if (!tasks[taskIndex].task()) {
					var n = tasks[taskIndex].taskName;
			
					queuedTasks = queuedTasks.filter(function(e) {
						(e.taskParent == n) && tasks.push({
							taskName: e.taskName,
							task: e.task
						});
						return e.taskParent != n;
					});
			
					self.dispatch('killed', tasks.splice(taskIndex--, 1)[0]);
				}
		
				taskIndex++;
				effectiveTime = (new Date()).getTime() - startTime;
				return effectiveTime <= correctedFrameTime;
			};

			/**
			* Starts tasks execution.
			* @return {sigma.chronos} Returns itself.
			*/
			function runTasks() {
				isRunning = true;
				taskIndex = 0;
				framesCount = 0;
		
				startTime = (new Date()).getTime();
				currentTime = startTime;
		
				self.dispatch('start');
				self.dispatch('insertframe');
				insertFrame(frameInserter);
				return self;
			};

			/**
			* Stops tasks execution, and dispatch a "stop" event.
			* @return {sigma.chronos} Returns itself.
			*/
			function stopTasks() {
				self.dispatch('stop');
				isRunning = false;
				return self;
			};

			/**
			* A task is a function that will be executed continuously while it returns
			* true. As soon as it return false, the task will be removed.
			* If several tasks are present, they will be executed in parallele.
			* This method will add the task to this execution process.
			* @param {function(): boolean} task     The task to add.
			* @param {string} name                  The name of the worker, used for
			*                                       managing the different tasks.
			* @param {boolean} autostart            If true, sigma.chronos will start
			*                                       automatically if it is not working
			*                                       yet.
			* @return {sigma.chronos} Returns itself.
			*/
			function addTask(task, name, autostart) {
				if (typeof task != 'function') {
					throw new Error('Task "' + name + '" is not a function');
				}
	
				tasks.push({
					taskName: name,
					task: task
				});
	
				isRunning = !!(isRunning || (autostart && runTasks()) || true);
				return self;
			};

			/**
			* Will add a task that will be start to be executed as soon as a task
			* named as the parent will be removed.
			* @param {function(): boolean} task     The task to add.
			* @param {string} name                  The name of the worker, used for
			*                                       managing the different tasks.
			* @param {string} parent                The name of the parent task.
			* @return {sigma.chronos} Returns itself.
			*/
			function queueTask(task, name, parent) {
				if (typeof task != 'function') {
					throw new Error('Task "' + name + '" is not a function');
				}
				
				if (!tasks.concat(queuedTasks).some(function(e) {
					return e.taskName == parent;
				})) {
					throw new Error('Parent task "' + parent + '" of "' + name + '" is not attached.');
				}

				queuedTasks.push({
					taskParent: parent,
					taskName: name,
					task: task
				});
		
				return self;
			};

			/**
			* Removes a task.
			* @param  {string} v           If v is undefined, then every tasks will
			*                              be removed. If not, each task named v will
			*                              be removed.
			* @param  {number} queueStatus Determines the queued tasks behaviour. If 0,
			*                              then nothing will happen. If 1, the tasks
			*                              queued to any removed task will be triggered.
			*                              If 2, the tasks queued to any removed task
			*                              will be removed as well.
			* @return {sigma.chronos} Returns itself.
			*/
			function removeTask(v, queueStatus) {
				if (v == undefined) {
					tasks = [];
					if (queueStatus == 1) {
						queuedTasks = [];
					}else if (queueStatus == 2) {
						tasks = queuedTasks;
						queuedTasks = [];
					}
					stopTasks();
				} else {
					var n = (typeof v == 'string') ? v : '';
					tasks = tasks.filter(function(e) {
						if ((typeof v == 'string') ? e.taskName == v : e.task == v) {
							n = e.taskName;
							return false;
						}
						return true;
					});
			
					if (queueStatus > 0) {
						queuedTasks = queuedTasks.filter(function(e) {
						if (queueStatus == 1 && e.taskParent == n) {
							tasks.push(e);
						}
						return e.taskParent != n;
						});
					}
				}
		
				isRunning = !!(!tasks.length || (stopTasks() && false));
				return self;
			};

			/**
			* A generator is a pair task/condition. The task will be executed
			* while it returns true.
			* When it returns false, the condition will be tested. If
			* the condition returns true, the task will be executed
			* again at the next process iteration. If not, the generator
			* is removed.
			* If several generators are present, they will be executed one
			* by one: When the first stops, the second will start, etc. When
			* they are all ended, then the conditions will be tested to know
			* which generators have to be started again.
			* @param {string} id                     The generators ID.
			* @param {function(): boolean} task      The generator's task.
			* @param {function(): boolean} condition The generator's condition.
			* @return {sigma.chronos} Returns itself.
			*/
			function addGenerator(id, task, condition) {
				if (generators[id] != undefined) {
					return self;
				}
		
				generators[id] = {
					task: task,
					condition: condition
				};
		
				getGeneratorsCount(true) == 0 && startGenerators();
				return self;
			};
	
			/**
			* Removes a generator. It means that the task will continue being executed
			* until it returns false, but then the
			* condition will not be tested.
			* @param  {string} id The generator's ID.
			* @return {sigma.chronos} Returns itself.
			*/
			function removeGenerator(id) {
				if (generators[id]) {
					generators[id].on = false;
					generators[id].del = true;
				}
				return self;
			};
	
			/**
			* Returns the number of generators.
			* @private
			* @param  {boolean} running If true, returns the number of active
			*                          generators instead.
			* @return {sigma.chronos} Returns itself.
			*/
			function getGeneratorsCount(running) {
				return running ?Object.keys(generators).filter(function(id) {
					return !!generators[id].on;
				}).length :Object.keys(generators).length;
			};
	
			/**
			* Returns the array of the generators IDs.
			* @return {array.<string>} The array of IDs.
			*/
			function getGeneratorsIDs() {
				return Object.keys(generators);
			}
	
			/**
			* startGenerators is the method that manages which generator
			* is the next to start when another one stops. It will dispatch
			* a "stopgenerators" event if there is no more generator to start,
			* and a "startgenerators" event else.
			* @return {sigma.chronos} Returns itself.
			*/
			function startGenerators() {
				if (!Object.keys(generators).length) {
					self.dispatch('stopgenerators');
				}else {
					self.dispatch('startgenerators');

					self.unbind('killed', onTaskEnded);
					insertFrame(function() {
						for (var k in generators) {
							generators[k].on = true;
							addTask(generators[k].task, k, false);
						}
					});
		
					self.bind('killed', onTaskEnded).runTasks();
				}
	
				return self;
			};

			/**
			* A callback triggered everytime the task of a generator stops, that will
			* test the related generator's condition, and see if there is still any
			* generator to start.
			* @private
			* @param  {Object} e The sigma.chronos "killed" event.
			*/
			function onTaskEnded(e) {
				if (generators[e['content'].taskName] != undefined) {
					if (generators[e['content'].taskName].del || !generators[e['content'].taskName].condition()) {
						delete generators[e['content'].taskName];
					}else {
						generators[e['content'].taskName].on = false;
					}
			
					if (getGeneratorsCount(true) == 0) {
						startGenerators();
					}
				}
			};

			/**
			* Either set or returns the fpsReq property. This property determines
			* the number of frames that should be inserted per second.
			* @param  {?number} v The frequency asked.
			* @return {(Chronos|number)} Returns the frequency if v is undefined, and
			*                          itself else.
			*/
			function frequency(v) {
				if (v != undefined) {
					fpsReq = Math.abs(1 * v);
					frameTime = 1000 / fpsReq;
					framesCount = 0;
					return self;
				} else {
					return fpsReq;
				}
			};

			/**
			* Returns the actual average number of frames that are inserted per
			* second.
			* @return {number} The actual average FPS.
			*/
			function getFPS() {
				if (isRunning) {
					lastFPS =Math.round(framesCount /((new Date()).getTime() - currentTime) *10000) / 10;
				}
		
				return lastFPS;
			};

			/**
			* Returns the number of tasks.
			* @return {number} The number of tasks.
			*/
			function getTasksCount() {
				return tasks.length;
			}
	
			/**
			* Returns the number of queued tasks.
			* @return {number} The number of queued tasks.
			*/
			function getQueuedTasksCount() {
				return queuedTasks.length;
			}
	
			/**
			* Returns how long sigma.chronos has active tasks running
			* without interuption for, in ms.
			* @return {number} The time chronos is running without interuption for.
			*/
			function getExecutionTime() {
				return startTime - currentTime;
			}

			this.frequency = frequency;
	
			this.runTasks = runTasks;
			this.stopTasks = stopTasks;
			this.insertFrame = insertFrame;
	
			this.addTask = addTask;
			this.queueTask = queueTask;
			this.removeTask = removeTask;
	
			this.addGenerator = addGenerator;
			this.removeGenerator = removeGenerator;
			this.startGenerators = startGenerators;
			this.getGeneratorsIDs = getGeneratorsIDs;
	
			this.getFPS = getFPS;
			this.getTasksCount = getTasksCount;
			this.getQueuedTasksCount = getQueuedTasksCount;
			this.getExecutionTime = getExecutionTime;
	
			return this;
		})();

		//////////////////debug/////////////////
		sigma.debugMode = 0;

		sigma.log = function() {
			if(sigma.debugMode == 1) {
				for(var k in arguments) {
					console.log(arguments[k]);
				}
			} else if(sigma.debugMode > 1) {
				for(var k in arguments) {
					throw new Error(arguments[k]);
				}
			}

			return sigma;
		};

		/////////////////easing////////////////////////
		sigma.easing = {
			linear : {},
			quadratic : {}
		};

		sigma.easing.linear.easenone = function(k) {
			return k;
		};

		sigma.easing.quadratic.easein = function(k) {
			return k * k;
		};

		sigma.easing.quadratic.easeout = function(k) {
			return -k * (k - 2);
		};

		sigma.easing.quadratic.easeinout = function(k) {
			if((k *= 2) < 1)
				return 0.5 * k * k;
			return -0.5 * (--k * (k - 2) - 1);
		};

		////////////////////tools/////////////////////
		sigma.tools.drawRoundRect = function(ctx, x, y, w, h, ellipse, corners) {
			var e = ellipse ? ellipse : 0;
			var c = corners ? corners : [];
			c = (( typeof c) == 'string') ? c.split(' ') : c;

			var tl = e && (c.indexOf('topleft') >= 0 || c.indexOf('top') >= 0 || c.indexOf('left') >= 0);
			var tr = e && (c.indexOf('topright') >= 0 || c.indexOf('top') >= 0 || c.indexOf('right') >= 0);
			var bl = e && (c.indexOf('bottomleft') >= 0 || c.indexOf('bottom') >= 0 || c.indexOf('left') >= 0);
			var br = e && (c.indexOf('bottomright') >= 0 || c.indexOf('bottom') >= 0 || c.indexOf('right') >= 0);

			ctx.moveTo(x, y + e);

			if(tl) {
				ctx.arcTo(x, y, x + e, y, e);
			} else {
				ctx.lineTo(x, y);
			}

			if(tr) {
				ctx.lineTo(x + w - e, y);
				ctx.arcTo(x + w, y, x + w, y + e, e);
			} else {
				ctx.lineTo(x + w, y);
			}

			if(br) {
				ctx.lineTo(x + w, y + h - e);
				ctx.arcTo(x + w, y + h, x + w - e, y + h, e);
			} else {
				ctx.lineTo(x + w, y + h);
			}

			if(bl) {
				ctx.lineTo(x + e, y + h);
				ctx.arcTo(x, y + h, x, y + h - e, e);
			} else {
				ctx.lineTo(x, y + h);
			}

			ctx.lineTo(x, y + e);
		};

		sigma.tools.getRGB = function(s, asArray) {
			s = s.toString();
			var res = {
				'r' : 0,
				'g' : 0,
				'b' : 0
			};

			if(s.length >= 3) {
				if(s.charAt(0) == '#') {
					var l = s.length - 1;
					if(l == 6) {
						res = {
							'r' : parseInt(s.charAt(1) + s.charAt(2), 16),
							'g' : parseInt(s.charAt(3) + s.charAt(4), 16),
							'b' : parseInt(s.charAt(5) + s.charAt(5), 16)
						};
					} else if(l == 3) {
						res = {
							'r' : parseInt(s.charAt(1) + s.charAt(1), 16),
							'g' : parseInt(s.charAt(2) + s.charAt(2), 16),
							'b' : parseInt(s.charAt(3) + s.charAt(3), 16)
						};
					}
				}
			}

			if(asArray) {
				res = [res['r'], res['g'], res['b']];
			}

			return res;
		};

		sigma.tools.rgbToHex = function(R, G, B) {
			return sigma.tools.toHex(R) + sigma.tools.toHex(G) + sigma.tools.toHex(B);
		};

		sigma.tools.toHex = function(n) {
			n = parseInt(n, 10);

			if(isNaN(n)) {
				return '00';
			}
			n = Math.max(0, Math.min(n, 255));
			return '0123456789ABCDEF'.charAt((n - n % 16) / 16) + '0123456789ABCDEF'.charAt(n % 16);
		};

		///////////////////////////////////////sigmaoutro/////////////
		sigma.publicPrototype = SigmaPublic.prototype;
	})();

